/*
** DisCtl.c
** Disassembly control used to display a disassembly listing.
*/

#include <windows.h>
#include <windowsx.h>
#include <memory.h>
#include <string.h>
#include "disctl.h"

static void DisRefresh(HWND hWnd);

/*
** structure where buffer size and graphic mode characteristics are saved.
*/
typedef struct DisStruct
               {
               DIS_BUFFER     **pListing;    /* address of listing to draw */
               WORD           wSize;         /* number of buffer lists (size of pListing array) */
               WORD           wFactor;       /* how much lines represents a scroll of 1 unit */
               DWORD          dwScroll;      /* first line display (zero based) */
               WORD           wMaxScroll;    /* maximum scroll range */
               WORD           wMaxLines;     /* number of lines displayed in the window */
               HFONT          hFont;         /* font used to display dump */
               WORD           wFontWidth;    /* font width in pixels */
               WORD           wFontHeight;   /* font height in pixels */
               BOOL           bLineNumber;   /* display line number ? */
               } DisStruct;

/*
** returns number of lines of the listing.
*/
static DWORD DisGetNumberOfLines(DIS_BUFFER **pListing, WORD wSize)
{
DWORD dwNbLines;
WORD wNbListing;
DIS_BUFFER *pBuf;

     dwNbLines = 0;
     if (pListing)
          for (wNbListing = 0; wNbListing < wSize; wNbListing++)
               for (pBuf = pListing[wNbListing]; pBuf; pBuf = pBuf->pNext)
                    dwNbLines += (DWORD) pBuf->wNbLines;
     return dwNbLines;
}

/*
** WM_DIS_SET_SCROLL message
** set first line to display.
** dwScroll is a line number (8 bytes displayed per line).
** if bRefresh is TRUE, control is redrawn.
*/
static void DisSetScroll(HWND hWnd, BOOL bRefresh, DWORD dwScroll)
{
WORD wMaxLines;
WORD wFactor;
WORD wSize;
DIS_BUFFER **pListing;
DWORD dwNbLines;
DWORD dwMaxScroll;

     pListing = (DIS_BUFFER **) GetWindowLong(hWnd, FIELDOFFSET(DisStruct, pListing));
     wMaxLines = GetWindowWord(hWnd, FIELDOFFSET(DisStruct, wMaxLines));
     wSize = GetWindowWord(hWnd, FIELDOFFSET(DisStruct, wSize));
     dwNbLines = DisGetNumberOfLines(pListing, wSize);
     if (dwNbLines <= (DWORD) wMaxLines)
          dwMaxScroll = 1;
     else dwMaxScroll = dwNbLines - wMaxLines;
     if (dwScroll >= dwMaxScroll)
          dwScroll = dwMaxScroll;
     SetWindowLong(hWnd, FIELDOFFSET(DisStruct, dwScroll), (long) dwScroll);
     wFactor = GetWindowWord(hWnd, FIELDOFFSET(DisStruct, wFactor));
     SetScrollPos(hWnd, SB_VERT, (WORD) (dwScroll / wFactor), TRUE);
     if (bRefresh)
          DisRefresh(hWnd);
}

/*
** Initialize scroll bar position and range.
*/
static void DisInitScroll(HWND hWnd)
{
DWORD dwNbLines;
DWORD dwMaxScroll;
WORD wMaxScroll;
WORD wMaxLines;
WORD wFactor;
WORD wSize;
DIS_BUFFER **pListing;

     pListing = (DIS_BUFFER **) GetWindowLong(hWnd, FIELDOFFSET(DisStruct, pListing));
     wMaxLines = GetWindowWord(hWnd, FIELDOFFSET(DisStruct, wMaxLines));
     wSize = GetWindowWord(hWnd, FIELDOFFSET(DisStruct, wSize));
     dwNbLines = DisGetNumberOfLines(pListing, wSize);
     if (dwNbLines <= (DWORD) wMaxLines)
          dwMaxScroll = 1;
     else dwMaxScroll = dwNbLines - wMaxLines;
     wFactor = (WORD) (dwMaxScroll / 32500) + 1;
     if (wFactor == 0)
          wFactor = 1;
     wMaxScroll = (WORD) (dwMaxScroll / wFactor);
     SetWindowWord(hWnd, FIELDOFFSET(DisStruct, wFactor), wFactor);
     SetWindowWord(hWnd, FIELDOFFSET(DisStruct, wMaxScroll), wMaxScroll);
     SetScrollRange(hWnd, SB_VERT, 0, wMaxScroll, FALSE);
     DisSetScroll(hWnd, 0, FALSE);
     EnableWindow(hWnd, (dwNbLines > (DWORD) wMaxLines));
}

/*
** set number of buffer lists to display.
** wSize is the number of buffer lists.
** This function does not redraw control.
*/
static void DisSetSize(HWND hWnd, WORD wSize)
{
     SetWindowWord(hWnd, FIELDOFFSET(DisStruct, wSize), wSize);
     DisInitScroll(hWnd);
}

/*
** WM_DIS_SET_BUFFER message
** set buffer size and pointer
** This function does not redraw control.
*/
static void DisSetBuffer(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
     SetWindowLong(hWnd, FIELDOFFSET(DisStruct, pListing), lParam);
     DisSetSize(hWnd, wParam);
}

/*
** WM_DIS_SET_LINENUM message
** bLineNumber is TRUE is line numbers should be displayed. 
** if bRefresh is TRUE, control is redrawn.
*/
static void DisSetLineNum(HWND hWnd, BOOL bLineNumber, BOOL bRefresh)
{
     SetWindowWord(hWnd, FIELDOFFSET(DisStruct, bLineNumber), bLineNumber);
     if (bRefresh)
          DisRefresh(hWnd);
}

/*
** WM_SETFONT message
** set font to use.
** if bRefresh is TRUE, control is redrawn.
*/
static void DisSetFont(HWND hWnd, HFONT hFont, BOOL bRefresh)
{
HDC hDC;
TEXTMETRIC tm;
HFONT hOldFont;
WORD wMaxLines;
RECT rc;

     SetWindowWord(hWnd, FIELDOFFSET(DisStruct, hFont), hFont);
     if (hDC = GetDC(hWnd))
          {
          if (hFont)
               hOldFont = SelectObject(hDC, hFont);
          GetTextMetrics(hDC, &tm);
          if (hFont)
               SelectObject(hDC, hOldFont);
          GetClientRect(hWnd, &rc);
          wMaxLines = rc.bottom / tm.tmHeight;
          SetWindowWord(hWnd, FIELDOFFSET(DisStruct, wFontWidth), tm.tmAveCharWidth);
          SetWindowWord(hWnd, FIELDOFFSET(DisStruct, wFontHeight), tm.tmHeight);
          SetWindowWord(hWnd, FIELDOFFSET(DisStruct, wMaxLines), wMaxLines);
          DisInitScroll(hWnd);
          ReleaseDC(hWnd, hDC);
          }
     if (bRefresh)
          DisRefresh(hWnd);
}

/*
** WM_SIZE message
** Determine new number of lines in the window.
*/
static long DisSize(HWND hWnd)
{
WORD wFontHeight;
WORD wMaxLines;
RECT rc;

     wFontHeight = GetWindowWord(hWnd, FIELDOFFSET(DisStruct, wFontHeight));
     GetClientRect(hWnd, &rc);
     wMaxLines = rc.bottom / wFontHeight;
     SetWindowWord(hWnd, FIELDOFFSET(DisStruct, wMaxLines), wMaxLines);
     DisInitScroll(hWnd);
     return NULL;
}

/*
** WM_CREATE message
** Initialization of the window private structure in the extra bytes.
** The default setting is no buffer (0 byte long) with system font.
*/
static long DisCreate(HWND hWnd)
{
     DisSetBuffer(hWnd, 0, NULL);
     DisSetScroll(hWnd, 0, FALSE);
     DisSetFont(hWnd, 0, FALSE);
     DisSize(hWnd);
     return 0;
}

/*
** repaint all control.
*/
static void DisPrintAll(HWND hWnd, HDC hDC)
{
HFONT hFont;
HFONT hOldFont;
WORD wLineInBuf;
DWORD dwLine;
WORD wLen;
WORD wRow;
DWORD dwScroll;
WORD wNbLines;
WORD wFontWidth;
WORD wFontHeight;
WORD wNbListing;
WORD wSize;
BYTE *pDis;
BOOL bLineNumber;
DIS_BUFFER **pListing;
DIS_BUFFER *pBuf;
char szBuf[128];
RECT rc;

     /*
     ** get buffer characteristics from window extra bytes.
     */
     pListing = (DIS_BUFFER **) GetWindowLong(hWnd, FIELDOFFSET(DisStruct, pListing));
     wSize = GetWindowWord(hWnd, FIELDOFFSET(DisStruct, wSize));
     bLineNumber = GetWindowWord(hWnd, FIELDOFFSET(DisStruct, bLineNumber));
     dwScroll = (DWORD) GetWindowLong(hWnd, FIELDOFFSET(DisStruct, dwScroll));
     wFontWidth = GetWindowWord(hWnd, FIELDOFFSET(DisStruct, wFontWidth));
     wFontHeight = GetWindowWord(hWnd, FIELDOFFSET(DisStruct, wFontHeight));
     GetClientRect(hWnd, &rc);
     wRow = rc.right / wFontWidth;
     wNbLines = rc.bottom / wFontHeight;

     /*
     ** select font and print all lines
     */
     if (hFont = GetWindowWord(hWnd, FIELDOFFSET(DisStruct, hFont)))
          hOldFont = SelectObject(hDC, hFont);
     dwLine = 0;
     if (pListing)
          for (wNbListing = 0; wNbListing < wSize; wNbListing++)
               for (pBuf = pListing[wNbListing]; pBuf; pBuf = pBuf->pNext)
                    if (pDis = pBuf->pDis)
                         for (wLineInBuf = 0; wLineInBuf < pBuf->wNbLines; wLineInBuf++)
                              {
                              if ((dwLine >= dwScroll) && (dwLine - dwScroll < wNbLines))
                                   {
                                   dwLine++;
                                   if (bLineNumber)
                                        wsprintf(szBuf, "%04lu %s", dwLine, pDis);
                                   else strcpy(szBuf, pDis);
                                   for (wLen = strlen(szBuf); wLen < wRow; wLen++)
                                        szBuf[wLen] = ' ';
                                   szBuf[wLen] = 0;
                                   TextOut(hDC, 1, ((WORD) (dwLine - dwScroll - 1)) * wFontHeight + 2, szBuf, strlen(szBuf));
                                   }
                              else dwLine++;
                              pDis += strlen(pDis) + 1;
                              }
     if (hFont)
          SelectObject(hDC, hOldFont);
}

/*
** WM_PAINT message
** redraw the buffer
*/
static long DisPaint(HWND hWnd)
{
PAINTSTRUCT ps;
HDC hDC;

     hDC = BeginPaint(hWnd, &ps);
     DisPrintAll(hWnd, hDC);
     EndPaint(hWnd, &ps);
     return NULL;
}

/*
** WM_DIS_REFRESH message
** refresh window
*/
static void DisRefresh(HWND hWnd)
{
HDC hDC;

     hDC = GetDC(hWnd);
     if (hDC)
          {
          DisPrintAll(hWnd, hDC);
          ReleaseDC(hWnd, hDC);
          }
}

/*
** WM_VSCROLL message
*/
static long DisVScroll(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
DWORD dwScroll;
WORD wScroll;
WORD wOldScroll;
WORD wMaxScroll;
WORD wFactor;
WORD wFontHeight;
WORD wNbLines;
RECT rc;

     dwScroll = GetWindowLong(hWnd, FIELDOFFSET(DisStruct, dwScroll));
     wFactor = GetWindowWord(hWnd, FIELDOFFSET(DisStruct, wFactor));
     wOldScroll = wScroll = (WORD) (dwScroll / wFactor);
     wMaxScroll = GetWindowWord(hWnd, FIELDOFFSET(DisStruct, wMaxScroll));
     wFontHeight = GetWindowWord(hWnd, FIELDOFFSET(DisStruct, wFontHeight));
     GetClientRect(hWnd, &rc);
     wNbLines = rc.bottom / wFontHeight;
     switch (wParam)
          {
          case SB_TOP:
               if (wScroll)
                    wScroll = 0;
               break;

          case SB_BOTTOM:
               if (wScroll != wMaxScroll)
                    wScroll = wMaxScroll;
               break;

          case SB_LINEUP:
               if (wScroll)
                    wScroll--;
               break;

          case SB_LINEDOWN:
               if (wScroll != wMaxScroll)
                    wScroll++;
               break;

          case SB_PAGEUP:
               if (wScroll)
                    wScroll -= min(wScroll, wNbLines);
               break;

          case SB_PAGEDOWN:
               if (wScroll != wMaxScroll)
                    wScroll += min(wMaxScroll - wScroll, wNbLines);
               break;

          case SB_THUMBPOSITION:
          case SB_THUMBTRACK:
               if (wScroll != LOWORD(lParam))
                    wScroll = LOWORD(lParam);
               break;
          }
     if (wOldScroll != wScroll)
          {
          dwScroll = (DWORD) wScroll * wFactor;
          DisSetScroll(hWnd, TRUE, dwScroll);
          SendMessage(GetParent(hWnd), WM_COMMAND, GetDlgCtrlID(hWnd), MAKELONG(hWnd, DIS_SCROLL_CHANGED));
          }
     return NULL;
}

/*
** Dis window proc.
*/
long CALLBACK __export DisWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     switch (message)
          {
          case WM_DIS_SET_BUFFER:
               DisSetBuffer(hWnd, wParam, lParam);
               break;

          case WM_DIS_SET_LINENUM:
               DisSetLineNum(hWnd, (BOOL) wParam, (BOOL) lParam);
               break;

          case WM_DIS_GET_LINENUM:
               return (DWORD) GetWindowWord(hWnd, FIELDOFFSET(DisStruct, bLineNumber));

          case WM_DIS_SET_SCROLL:
               DisSetScroll(hWnd, (BOOL) wParam, (DWORD) lParam);
               break;

          case WM_DIS_GET_SCROLL:
               return (DWORD) GetWindowLong(hWnd, FIELDOFFSET(DisStruct, dwScroll));

          case WM_DIS_REFRESH:
               DisRefresh(hWnd);
               break;

          case WM_SETFONT:
               DisSetFont(hWnd, (HFONT) wParam, (BOOL) lParam);
               break;

          case WM_GETFONT:
               return (long) (DWORD) GetWindowWord(hWnd, FIELDOFFSET(DisStruct, hFont));

          case WM_CREATE:
               return DisCreate(hWnd);

          case WM_SIZE:
               return DisSize(hWnd);

          case WM_PAINT:
               return DisPaint(hWnd);

          case WM_VSCROLL:
               return DisVScroll(hWnd, wParam, lParam);

          default:
               return DefWindowProc(hWnd, message, wParam, lParam);
          }
     return NULL;
}

BOOL CALLBACK __export DisInit(HINSTANCE hInst)
{
WNDCLASS wc;

     /*
     ** Fill in window class structure with parameters that describe the
     ** disassembly window.
     */
     wc.style = NULL;
     wc.lpfnWndProc = DisWndProc;
     wc.cbClsExtra = 0;
     wc.cbWndExtra = sizeof(DisStruct);
     wc.hInstance = hInst;
     wc.hIcon = NULL;
     wc.hCursor = LoadCursor(NULL, IDC_ARROW);
     wc.hbrBackground = GetStockObject(WHITE_BRUSH);
     wc.lpszMenuName = NULL;
     wc.lpszClassName = "DisCtrlClass";

     /*
     ** Register the window class.
     */
     return RegisterClass(&wc);
}

/*
** DLL entry point
*/
BOOL CALLBACK LibMain(HINSTANCE hinst, UINT wDS, UINT cbHeap, DWORD unused)
{
     return TRUE;
}

/*
** DLL exit point
*/
int FAR PASCAL _WEP(int unused)
{
     return TRUE;
}
